/*
 * Copyright (c) 1999-2004 University of New South Wales
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#pragma once

#include <types.h>

#define R_AARCH64_NONE            0	/* No relocation.  */
#define R_AARCH64_RELATIVE     1027	/* Adjust by program base.  */
#define ELF64_R_TYPE(i)			((i) & 0xffffffff)
/*
 * File header
 */
struct Elf64_Header {
    unsigned char   e_ident[16];
    uint16_t        e_type;	/* Relocatable=1, Executable=2 (+ some
				 * more ..) */
    uint16_t        e_machine;	/* Target architecture: MIPS=8 */
    uint32_t        e_version;	/* Elf version (should be 1) */
    uint64_t        e_entry;	/* Code entry point */
    uint64_t        e_phoff;	/* Program header table */
    uint64_t        e_shoff;	/* Section header table */
    uint32_t        e_flags;	/* Flags */
    uint16_t        e_ehsize;	/* ELF header size */
    uint16_t        e_phentsize;	/* Size of one program segment
					 * header */
    uint16_t        e_phnum;	/* Number of program segment
					 * headers */
    uint16_t        e_shentsize;	/* Size of one section header */
    uint16_t        e_shnum;	/* Number of section headers */
    uint16_t        e_shstrndx;	/* Section header index of the
					 * string table for section header
					 * * names */
};

/*
 * Section header
*/
struct Elf64_Shdr {
    uint32_t        sh_name;
    uint32_t        sh_type;
    uint64_t        sh_flags;
    uint64_t        sh_addr;
    uint64_t        sh_offset;
    uint64_t        sh_size;
    uint32_t        sh_link;
    uint32_t        sh_info;
    uint64_t        sh_addralign;
    uint64_t        sh_entsize;
};

/*
 * Program header
 */
struct Elf64_Phdr {
    uint32_t        p_type;	/* Segment type: Loadable segment = 1 */
    uint32_t        p_flags;	/* Flags: logical "or" of PF_
					 * constants below */
    uint64_t        p_offset;	/* Offset of segment in file */
    uint64_t        p_vaddr;	/* Reqd virtual address of segment
					 * when loading */
    uint64_t        p_paddr;	/* Reqd physical address of
					 * segment */
    uint64_t        p_filesz;	/* How many bytes this segment
					 * occupies in file */
    uint64_t        p_memsz;	/* How many bytes this segment
					 * should occupy in * memory (when
					 * * loading, expand the segment
					 * by * concatenating enough zero
					 * bytes to it) */
    uint64_t        p_align;	/* Reqd alignment of segment in
					 * memory */
};

/*
 * Dynamic section
 */
struct Elf64_Dyn {
    uint64_t d_tag;
    union {
        uint64_t d_val;
        uint64_t d_ptr;
    } d_un;
};

struct Elf64_Rela {
    uint64_t r_offset;		/* Address */
    uint64_t r_info;			/* Relocation type and symbol index */
    uint64_t r_addend;		/* Addend */
};

int elf64_checkFile(void *elfFile);
struct Elf64_Phdr * elf64_getProgramSegmentTable(void *elfFile);
unsigned elf64_getNumSections(void *elfFile);
char * elf64_getStringTable(void *elfFile, int string_segment);
char * elf64_getSegmentStringTable(void *elfFile);

/* Assume the field is at least 4-byte aligned and little-endian */
static uint64_t elf64_read64(void *addr)
{
    uint64_t ret;
    if (((uintptr_t)addr) % 8 == 0) {
        ret = *((uint64_t *)addr);
    } else {
        ret = *((uint32_t *)(((uintptr_t)addr) + 4));
        ret = ret << 32;
        ret |= *((uint32_t *)addr);
    }
    return ret;
}

static inline struct Elf64_Shdr *
elf64_getSectionTable(struct Elf64_Header *file) {
    /* Cast heaven! */
    return (struct Elf64_Shdr*) (uintptr_t) (((uintptr_t) file) + file->e_shoff);
}

/* accessor functions */
static inline uint32_t
elf64_getSectionType(struct Elf64_Header *file, uint16_t s)
{
    return elf64_getSectionTable(file)[s].sh_type;
}

static inline uint32_t
elf64_getSectionFlags(struct Elf64_Header *file, uint16_t s)
{
    return elf64_getSectionTable(file)[s].sh_flags;
}

char * elf64_getSectionName(void *elfFile, int i);
uint64_t elf64_getSectionSize(void *elfFile, int i);
uint64_t elf64_getSectionAddr(struct Elf64_Header *elfFile, int i);
void * elf64_getSection(void *elfFile, int i);
void * elf64_getSectionNamed(void *elfFile, char *str);
int elf64_getSegmentType (void *elfFile, int segment);
void elf64_getSegmentInfo(void *elfFile, int segment, uint64_t *p_vaddr,
                          uint64_t *p_paddr, uint64_t *p_filesz,
                          uint64_t *p_offset, uint64_t *p_memsz);
void elf64_showDetails(void *elfFile, int size, char *name);
uint64_t elf64_getEntryPoint (struct Elf64_Header *elfFile);

/* Program Headers functions */
/* Program header functions */
uint16_t elf64_getNumProgramHeaders(struct Elf64_Header *file);

static inline struct Elf64_Phdr *
elf64_getProgramHeaderTable(struct Elf64_Header *file) {
    /* Cast hell! */
    uint64_t e_phoff = elf64_read64(&file->e_phoff);
    return (struct Elf64_Phdr*) (uintptr_t) (((uintptr_t) file) + e_phoff);
}

/* accessor functions */
static inline uint32_t
elf64_getProgramHeaderFlags(struct Elf64_Header *file, uint16_t ph)
{
    return elf64_getProgramHeaderTable(file)[ph].p_flags;
}

static inline uint32_t
elf64_getProgramHeaderType(struct Elf64_Header *file, uint16_t ph)
{
    return elf64_getProgramHeaderTable(file)[ph].p_type;
}

static inline uint64_t
elf64_getProgramHeaderFileSize(struct Elf64_Header *file, uint16_t ph)
{
    struct Elf64_Phdr  *phdr = &elf64_getProgramHeaderTable(file)[ph];
    return elf64_read64(&phdr->p_filesz);
}

static inline uint64_t
elf64_getProgramHeaderMemorySize(struct Elf64_Header *file, uint16_t ph)
{
    struct Elf64_Phdr  *phdr = &elf64_getProgramHeaderTable(file)[ph];
    return elf64_read64(&phdr->p_memsz);
}

static inline uint64_t
elf64_getProgramHeaderVaddr(struct Elf64_Header *file, uint16_t ph)
{
    struct Elf64_Phdr  *phdr = &elf64_getProgramHeaderTable(file)[ph];
    return elf64_read64(&phdr->p_vaddr);
}

static inline uint64_t
elf64_getProgramHeaderPaddr(struct Elf64_Header *file, uint16_t ph)
{
    struct Elf64_Phdr  *phdr = &elf64_getProgramHeaderTable(file)[ph];
    return elf64_read64(&phdr->p_paddr);
}

static inline uint64_t
elf64_getProgramHeaderOffset(struct Elf64_Header *file, uint16_t ph)
{
    struct Elf64_Phdr  *phdr = &elf64_getProgramHeaderTable(file)[ph];
    return elf64_read64(&phdr->p_offset);
}

